﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Threading;
using System.Globalization;
using System.Threading;
using System.Collections.ObjectModel;
using System.Data;
using System.Xml;
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
using System.IO;

namespace ResourceBlender_Express {
    /// <summary>
    /// A window for editing Resx files.
    /// </summary>
    public partial class WinEditResx : Window {
        private BackgroundWorker worker;
        private DispatcherTimer timer;
        private int autoSaveInterval;
        private DateTime lastSave;

        private ObservableCollection<ResxResource> resources;
        private string fileName;

        // used for auto translation, this is just a CheckBoxMenuItem implementation/hack
        private MenuItem sourceLang,
                         destinationLang;

        /// <summary>
        /// Initializes a new <see cref="WinEditResx"/> and loads the specified Resx file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        public WinEditResx(string fileName) {
            InitializeComponent();
            this.fileName = fileName;
            this.autoSaveInterval = ResourceBlender_Express.Properties.Settings.Default.AutoSaveInterval;
            timer = new DispatcherTimer(TimeSpan.FromMilliseconds(autoSaveInterval), DispatcherPriority.Background, new EventHandler(DoAutoSave), this.Dispatcher);

            string destinationCode = string.Empty;
            string fileNameNoExt = System.IO.Path.GetFileNameWithoutExtension(fileName);
            int splitter = fileNameNoExt.LastIndexOf('.');
            string fileLang = string.Empty;
            if(splitter > 0) { // file has a language code
                fileLang = fileNameNoExt.Substring(splitter + 1).Replace('.', '-');
                try {
                    CultureInfo fileLangInfo = Utils.GetCultureInfo(fileLang);
                    string googleCode = GoogleTranslator.ISOCode2GoogleCode(fileLangInfo.Name);
                    if(GoogleTranslator.GoogleSupportedLanguages.ContainsKey(googleCode)) {
                        destinationCode = googleCode;
                    } else {
                        destinationCode = "en";
                    }
                } catch(Exception ex) {
                    Console.WriteLine(ex.StackTrace);
                }
            }
            
            RoutedEventHandler setSourceLanguage = new RoutedEventHandler(SetSourceLanguage),
                               setDestinationLanguage = new RoutedEventHandler(SetDestinationLanguage);
            foreach(KeyValuePair<string, string> lang in GoogleTranslator.GoogleSupportedLanguages) { // <code, name>
                MenuItem itmSrc = new MenuItem(),
                         itmDest = new MenuItem();
                itmSrc.Header = lang.Value;
                itmSrc.CommandParameter = lang.Key;
                itmSrc.Click += setSourceLanguage;
                mnuSrcLanguage.Items.Add(itmSrc);
                itmDest.Header = lang.Value;
                itmDest.CommandParameter = lang.Key;
                itmDest.Click += setDestinationLanguage;
                mnuDestLanguage.Items.Add(itmDest);
                if(lang.Key.ToLower() == destinationCode) {
                    itmDest.IsChecked = true;
                    destinationLang = itmDest;
                }
                if(lang.Key.ToLower() == "en") {
                    itmSrc.IsChecked = true;
                    sourceLang = itmSrc;
                }
            }
        }

        /// <summary>
        /// Sets the source language for auto-translation.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void SetSourceLanguage(object sender, RoutedEventArgs e) {
            if(sourceLang != null) {
                sourceLang.IsChecked = false;
            }
            sourceLang = sender as MenuItem;
            sourceLang.IsChecked = true;
        }

        /// <summary>
        /// Sets the destination language for auto-translation.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void SetDestinationLanguage(object sender, RoutedEventArgs e) {
            if(destinationLang != null) {
                destinationLang.IsChecked = false;
            }
            destinationLang = sender as MenuItem;
            destinationLang.IsChecked = true;
        }

        /// <summary>
        /// Triggers the initial load of the file.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            if(!File.Exists(fileName)) {
                DialogBox.ShowAlert(this, "The specified file does not exist", "Error");
                Close();
            } else {
                RefreshResources();
            }
        }

        /// <summary>
        /// Reloads the file.
        /// </summary>
        private void RefreshResources() {
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
            worker = new BackgroundWorker();
            int selectedIndex = dgResources.SelectedIndex;
            worker.DoWork += (s, dwe) => {
                ObservableCollection<ResxResource> resourcesTemp = new ObservableCollection<ResxResource>();
                List<string> errors = new List<string>();
                XmlDocument doc = new XmlDocument();
                try {
                    doc.Load(fileName);
                } catch(Exception ex) {
                    throw new ResourceBlenderException(string.Format("Invalid XML file: {0}", fileName), ex);
                }
                if(doc.ChildNodes.Count == 0) {
                    throw new ResourceBlenderException(string.Format("Invalid resx file: {0}", fileName));
                }
                XmlNodeList nodes = doc.SelectNodes("/root/data");
                XmlAttributeCollection attr;
                foreach(XmlNode node in nodes) {
                    if(node.NodeType == XmlNodeType.Element) {
                        attr = node.Attributes;
                        ResxResource resource = new ResxResource();
                        if(attr["name"] == null) {
                            errors.Add(string.Format("Invalid element in file {0}: name attribute missing", ""));
                            continue;
                        } else {
                            resource.Name = attr["name"].Value;
                        }
                        if(attr["type"] != null) {
                            resource.Type = attr["type"].Value;
                        } else if(attr["mimetype"] != null) {
                            resource.MimeType = attr["mimetype"].Value;
                        }
                        foreach(XmlNode child in node) {
                            if(child.NodeType == XmlNodeType.Element) {
                                switch(child.Name.ToLower()) {
                                    case "value":
                                        resource.Translation = child.InnerText;
                                        break;
                                    case "comment":
                                        resource.Comment = child.InnerText;
                                        break;
                                }
                            }
                        }
                        resourcesTemp.Add(resource);
                    }
                }
                if(errors.Count > 0) {
                    dwe.Result = new KeyValuePair<MultipleException, ObservableCollection<ResxResource>>(new MultipleException(errors), resourcesTemp);
                } else {
                    dwe.Result = resourcesTemp;
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                dgResources.IsEnabled = true;
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, string.Format("Error loading file: {0}", rwe.Error.Message), "Error");
                    Close();
                } else {
                    if(rwe.Result is ObservableCollection<ResxResource>) {
                        resources = rwe.Result as ObservableCollection<ResxResource>;
                    } else if(rwe.Result is KeyValuePair<MultipleException, ObservableCollection<ResxResource>>) {
                        KeyValuePair<MultipleException, ObservableCollection<ResxResource>> actualResult = (KeyValuePair<MultipleException, ObservableCollection<ResxResource>>)rwe.Result;
                        DialogBox.Show(this, actualResult.Key, "Errors Loading File", DialogBox.DialogBoxIcon.Warning);
                        resources = actualResult.Value;
                    }
                    dgResources.ItemsSource = resources;
                    //dgResources.ItemsSource = GetResources();
                    lblDb.Text = fileName;
                    lastSave = DateTime.Now;
                    if(itmAutoSave.IsChecked) {
                        timer.IsEnabled = true;
                        timer.Start();
                    }
                    if(resources.Count - 1 > selectedIndex) {
                        dgResources.SelectedIndex = selectedIndex;
                    }
                }
                SetStatus(null, false);
            };
            dgResources.IsEnabled = false;
            SetStatus("Loading resources...", true);
            worker.RunWorkerAsync();
        }

        /*public ICollectionView GetResources() {
            if(resources == null) {
                resources = new ObservableCollection<ResxResource>();
            }
            return CollectionViewSource.GetDefaultView(resources);
        }*/

        /// <summary>
        /// Focuses and selects all text in the TextBox used for editing when it is selected.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void txt_Initialized(object sender, EventArgs e) {
            TextBox tb = sender as TextBox;
            if(tb != null) {
                tb.Focus();
                tb.SelectAll();
            }
        }

        #region Sorting and Filtering
        /*/// <summary>
        /// Applies filters to the grid.
        /// </summary>
        private void FilterGrid(object sender, ExecutedRoutedEventArgs e) {
            /tringBuilder filterBuffer = new StringBuilder();
            ICollectionView view = GetResources();
            if(e.Parameter == null || (e.Parameter as string) != "clear") {
                string name = txtNameFilter.Text,
                       translation = txtTranslationFilter.Text,
                       comment = txtCommentFilter.Text;
                if(!string.IsNullOrEmpty(name)) {
                    filterBuffer.Append("bundlename like ");
                    switch(cboBundleFilter.SelectedIndex) {
                        case 0:
                            filterBuffer.Append("'%").Append(bundle).Append("%'");
                            break;
                        case 1:
                            filterBuffer.Append("'").Append(bundle).Append("%'");
                            break;
                        case 2:
                            filterBuffer.Append("'%").Append(bundle).Append("'");
                            break;
                    }
                }
                if(!string.IsNullOrEmpty(element)) {
                    if(filterBuffer.Length > 0) {
                        filterBuffer.Append(" and ");
                    }
                    filterBuffer.Append("elementname like ");
                    switch(cboElementFilter.SelectedIndex) {
                        case 0:
                            filterBuffer.Append("'%").Append(element).Append("%'");
                            break;
                        case 1:
                            filterBuffer.Append("'").Append(element).Append("%'");
                            break;
                        case 2:
                            filterBuffer.Append("'%").Append(element).Append("'");
                            break;
                    }
                }
                if(!string.IsNullOrEmpty(elementComment)) {
                    if(filterBuffer.Length > 0) {
                        filterBuffer.Append(" and ");
                    }
                    filterBuffer.Append("elementcomment like ");
                    switch(cboElementCommentFilter.SelectedIndex) {
                        case 0:
                            filterBuffer.Append("'%").Append(elementComment).Append("%'");
                            break;
                        case 1:
                            filterBuffer.Append("'").Append(elementComment).Append("%'");
                            break;
                        case 2:
                            filterBuffer.Append("'%").Append(elementComment).Append("'");
                            break;
                    }
                }
            } else {
                txtNameFilter.Text = string.Empty;
                txtTranslationFilter.Text = string.Empty;
                txtCommentFilter.Text = string.Empty;
            }
            DataTable tbl = (lstGrid.DataContext as DataTable);
            tbl.DefaultView.RowFilter = filterBuffer.ToString();
            lstGrid.DataContext = tbl;
            int numRows = tbl.Rows.Count;
            int numExcluded = numRows - tbl.DefaultView.Count;
            SetStatus(string.Format("{0} {1}{2}", numRows, numRows == 1 ? "element" : "elements", numExcluded > 0 ? string.Format(" ({0} excluded by filters)", numExcluded) : ""), false);
        }

        
        /// <summary>
        /// Fires the FilterGrid command if 'Live Filters' is checked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void cboFilter_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(chkLiveFilter.IsChecked != true) {
                return;
            }
            ResourceBlenderCommands.FilterGrid.Execute(null, null);
        }

        /// <summary>
        /// Fires the FilterGrid command if 'Live Filters' is checked
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.TextChangedEventArgs"/> instance containing the event data.</param>
        private void txtFilter_TextChanged(object sender, TextChangedEventArgs e) {
            if(chkLiveFilter.IsChecked != true) {
                return;
            }
            ResourceBlenderCommands.FilterGrid.Execute(null, null);
        }
        
        /// <summary>
        /// Sorts the grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnColumnHeaderClick(object sender, RoutedEventArgs e) {
            if(e.OriginalSource is GridViewColumnHeader) { // sort the grid
                GridViewColumn column = ((GridViewColumnHeader)e.OriginalSource).Column;
                string sortPath = column.DisplayMemberBinding == null ? (column.CellTemplate.DataType ?? string.Empty).ToString() : (column.DisplayMemberBinding as Binding).Path.Path;
                if(sortPath.Length == 0) {
                    return; // no field specified
                }
                if(column.HeaderTemplate == ascSorted) {
                    column.HeaderTemplate = null;
                    gridSort = sortPath + " DESC"; ;
                } else if(column.HeaderTemplate == descSorted) {
                    column.HeaderTemplate = ascSorted;
                    gridSort = "";
                } else {
                    column.HeaderTemplate = ascSorted;
                    gridSort = sortPath + " ASC";
                }
                (lstGrid.DataContext as DataTable).DefaultView.Sort = gridSort;
            }
        }*/
        #endregion

        /// <summary>
        /// Always allows a command to execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = true;
        }

        /// <summary>
        /// Adds and focuses a new resource. (a new row is always visible (denoted by it being blank)
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AddResource(object sender, ExecutedRoutedEventArgs e) {
            int colIndex = 0;
            var rowToSelect = dgResources.Items[dgResources.Items.Count - 1];
            var colToSelect = dgResources.Columns[colIndex];
            int rowIndex = dgResources.Items.IndexOf(rowToSelect);
            // select the new cell
            dgResources.SelectedCells.Clear();
            dgResources.SelectedCells.Add(new DataGridCellInfo(rowToSelect, colToSelect));
            this.Dispatcher.BeginInvoke(new DispatcherOperationCallback((param) => {
                // get the new cell, set focus, then open for edit
                var cell = DataGridUtils.GetCell(dgResources, rowIndex, colIndex);
                cell.Focus();
                dgResources.BeginEdit();
                return null;
            }), DispatcherPriority.Background, new object[] { null });
        }

        /// <summary>
        /// Determines whether the 'Delete Resource' command can execute
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteDeleteResource(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = this.IsLoaded;// && dgResources.Items.Count > 0 && dgResources.SelectedValue != null;
        }

        /// <summary>
        /// Deletes the selected resource.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void DeleteResource(object sender, ExecutedRoutedEventArgs e) {
            if(dgResources.SelectedCells.Count == 0)
                return;
            if(DialogBox.Show(this, "Delete resource?", "Confirm Deletion", DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question, DialogBox.DialogBoxButton.No) == DialogBox.DialogBoxButton.Yes) {
                ResxResource selectedValue = dgResources.SelectedCells[0].Item as ResxResource;
                resources.Remove(selectedValue);
            }
        }

        /// <summary>
        /// Toggles AutoSave on and off.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void ToggleAutoSave(object sender, RoutedEventArgs e) {
            if(itmAutoSave.IsChecked) {
                timer.Start();
                timer.IsEnabled = true;
            } else {
                timer.Stop();
                timer.IsEnabled = false;
            }
            ResourceBlender_Express.Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Determines whether the Save command can execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteSave(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = this.IsLoaded && (worker == null || !worker.IsBusy);
        }

        /// <summary>
        /// Performs an auto-save
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void DoAutoSave(object sender, EventArgs e) {
            if((worker == null || (worker != null && !worker.IsBusy)) && DateTime.Now.Subtract(lastSave).TotalMilliseconds >= autoSaveInterval) {
                ApplicationCommands.Save.Execute(true, this);
            }
        }

        /// <summary>
        /// Saves the list of resources back to the .rbt file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            if(resources == null) {
                return;
            }
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += (s, pce) => { // if the worker finishes early
                SetStatus(pce.UserState.ToString(), false);
            };
            worker.DoWork += (s, dwe) => {
                XmlDocument resx = new XmlDocument();
                resx.LoadXml(Constants.ResxSkeleton);
                XmlNode root = resx.SelectSingleNode("/root");
                List<string> duplicateElements = new List<string>(); // todo: find a way to search column-wide for duplicates
                foreach(ResxResource resource in resources) {
                    XmlNode nodeResourceData = resx.CreateElement("data");
                    if(resources.Count(res => res.Name == resource.Name) > 1) {
                        duplicateElements.Add(resource.Name);
                        continue;
                    }
                    nodeResourceData.Attributes.SetNamedItem(resx.CreateAttribute("name")).InnerText = resource.Name;
                    if(resource.Translation.Length > 0) {
                        nodeResourceData.AppendChild(resx.CreateElement("value")).InnerText = resource.Translation;
                    }
                    if(resource.Comment.Length > 0) {
                        nodeResourceData.AppendChild(resx.CreateElement("comment")).InnerText = resource.Comment;
                    }
                    if(resource.Type.Length > 0) {
                        nodeResourceData.Attributes.SetNamedItem(resx.CreateAttribute("type")).InnerText = resource.Type;
                    }
                    if(resource.MimeType.Length > 0) {
                        nodeResourceData.Attributes.SetNamedItem(resx.CreateAttribute("mimetype")).InnerText = resource.MimeType;
                    }
                    root.AppendChild(nodeResourceData);
                }
                resx.Save(fileName);
                if(duplicateElements.Count > 0) {
                    dwe.Result = Utils.Implode(duplicateElements, ", ", false);
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null, false);
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, "Error: " + rwe.Error.Message, "Error Saving File");
                } else if(rwe.Result != null) {
                    if(e.Parameter == null) { // if the autosave timer triggered this save, the parameter is true. Don't warn on every autosave
                        DialogBox.Show(this, string.Format("The following resources have duplicate names and could not be saved: {0}", rwe.Result.ToString()) + "\n", "Warning", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Warning);
                    } else {
                        SetStatus("AutoSave failed, click 'Save' for details", false);
                    }
                }
                lastSave = DateTime.Now;
            };
            SetStatus("Saving...", true);
            worker.RunWorkerAsync(resources);
        }

        /// <summary>
        /// Focuses the next row when enter or return is pressed
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.Windows.Controls.DataGridRowEditEndingEventArgs"/> instance containing the event data.</param>
        private void dgResources_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e) {
            if(e.EditAction == DataGridEditAction.Commit && dgResources.SelectionUnit == DataGridSelectionUnit.Cell) {
                if(Keyboard.IsKeyDown(Key.Enter) || Keyboard.IsKeyDown(Key.Return)) {
                    // get the row and column indexes of the currently selected cell
                    int currentColIndex = dgResources.Columns.IndexOf(dgResources.CurrentColumn),
                        currentRowIndex = dgResources.Items.IndexOf(e.Row.Item);
                    var rowToSelect = dgResources.Items[currentRowIndex + 1]; // get the next row
                    var colToSelect = dgResources.Columns[currentColIndex];
                    int rowIndex = dgResources.Items.IndexOf(rowToSelect);
                    // select the new cell
                    dgResources.SelectedCells.Clear();
                    dgResources.SelectedCells.Add(new DataGridCellInfo(rowToSelect, colToSelect));
                    this.Dispatcher.BeginInvoke(new DispatcherOperationCallback((param) => {
                        // get the new cell, set focus, then open for edit
                        var cell = DataGridUtils.GetCell(dgResources, rowIndex, currentColIndex);
                        cell.Focus();
                        dgResources.BeginEdit();
                        return null;
                    }), DispatcherPriority.Background, new object[] { null });
                }
            }
        }

        #region Duplicate Checking
        /* 
         * Until the datagrid allows column wide validation this won't work
         * 
        bool adding = false;
        private void dgResources_InitNewItem(object sender, InitializingNewItemEventArgs e) {
            adding = true;
        }
        private void dgResources_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) {
            int numDuplicatesAllowed = adding ? 0: 1;// : 2;
            int numResources = 0;
            ContentPresenter pres = e.EditingElement as ContentPresenter;
            if(pres == null || !(pres.Content is ResxResource)) {
                return;
            }
            ResxResource editedResource = pres.Content as ResxResource;
            foreach(ResxResource res in resources) {
                if(res.Name == editedResource.Name) {
                    numResources++;
                }
            }
            Console.WriteLine(numResources +" of "+numDuplicatesAllowed);
            if(numResources > numDuplicatesAllowed) {
                DialogBox.ShowAlert(this, "", "Error");
                e.Cancel = true;
            } else {
                adding = false;
            }
        }*/
        #endregion

        /// <summary>
        /// Automatically translates using Google Translate. The CommandParameter should be an array containing the source CultureInfo and destination CultureInfo
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AutoTranslate(object sender, ExecutedRoutedEventArgs e) {
            if(sourceLang == null || destinationLang == null) {
                DialogBox.ShowAlert(this, "The source and destination languages must be selected", "Error");
                return;
            } else if(sourceLang == destinationLang) {
                DialogBox.ShowAlert(this, "The source and destination languages must be different", "Error");
                return;
            }
            string googleSourceCode = sourceLang.CommandParameter.ToString(),
                   googleDestinationCode = destinationLang.CommandParameter.ToString();
            TextBox currentTextbox = e.OriginalSource as TextBox;
            string textToTranslate = currentTextbox.Text.Trim();
            if(textToTranslate.Length == 0) {
                return;
            } else if(textToTranslate.Length > 4800) {
                DialogBox.ShowAlert(this, "Only strings up to 4800 characters in length can be translated", "Not Supported");
            } else {
                BackgroundWorker translateWorker = new BackgroundWorker();
                translateWorker.DoWork += (s, dwe) => {
                    try {
                        dwe.Result = GoogleTranslator.Translate(textToTranslate, googleSourceCode, googleDestinationCode);
                    } catch(Exception ex) {
                        dwe.Result = ex.Message;
                    }
                };
                translateWorker.RunWorkerCompleted += (s, rwe) => {
                    currentTextbox.IsEnabled = true;
                    SetStatus(null, false);
                    if(rwe.Result is GoogleTranslator.TranslationResponse) {
                        GoogleTranslator.TranslationResponse response = rwe.Result as GoogleTranslator.TranslationResponse;
                        if(response.Status == 200) {
                            currentTextbox.Text = response.Data.TranslatedText;
                        } else {
                            DialogBox.ShowAlert(this,
                                string.Format("Error {0}: {1}",
                                    response.Status,
                                    response.Status == 400 ? string.Format("{0} translation not supported by {1}\n\n{2}", sourceLang.Header, "Google", response.Details) : response.Details),
                                "Error translating resource");
                        }
                    } else {
                        DialogBox.ShowAlert(this, rwe.Result.ToString(), "Error translating resource");
                    }
                };
                currentTextbox.IsEnabled = false;
                SetStatus("Translating...", true);
                translateWorker.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Shows the larger editor.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowFullEditor(object sender, ExecutedRoutedEventArgs e) {
            TextBox source = e.OriginalSource as TextBox;
            if(source == null) {
                return;
            }
            WinFullEditor editor = new WinFullEditor("Editor", source.Text);
            editor.Owner = this;
            editor.ShowDialog();
            if(editor.DialogResult == true) {
                source.Text = editor.Text;
            }
        }

        /// <summary>
        /// Opens the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            Utils.OpenHelp("Editing.Resx.Files.html");
        }

        /// <summary>
        /// Shows a DialogBox containing shortcut mappings.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowShortcuts(object sender, ExecutedRoutedEventArgs e) {
            FlowDocument doc = new FlowDocument();
            List<KeyValuePair<string, string>> shortcuts = new List<KeyValuePair<string, string>>() {
                {new KeyValuePair<string,string>("Shortcut Key","Action")},
                {new KeyValuePair<string,string>("Ctrl + S","Save File")},
                {new KeyValuePair<string,string>("Ctrl + N","Add New Resource")},
                {new KeyValuePair<string,string>("Del","Delete Resource")},
                {new KeyValuePair<string,string>("F2","Edit Current Cell")},
                {new KeyValuePair<string,string>("Enter","Commit cell edit and move to the next row")},
                {new KeyValuePair<string,string>("Tab","Commit cell edit and move to the next column")},
                {new KeyValuePair<string,string>("Esc","Cancel Editing/Close Window")}
            };
            doc.Blocks.Add(WPFUtils.GenerateFlowTable(shortcuts));
            doc.IsEnabled = false;
            DialogBox.Show(this, doc, "Shortcut Keys", false, DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Information, DialogBox.DialogBoxButton.OK);
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        /// <summary>
        /// Sets the text on the status message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="showProgress"><c>true</c> if the progress bar should be shown, otherwise <c>false</c>.</param>
        private void SetStatus(string message, bool showProgress) {
            prgStatus.Visibility = showProgress ? Visibility.Visible : Visibility.Collapsed;
            if(message == null && resources != null) {
                int numResources = resources.Count;
                lblStatus.Text = string.Format("{0} {1}", numResources, numResources == 1 ? "resource" : "resources");
            } else {
                lblStatus.Text = message;
            }
        }
    }
}
